package com.caseeye.base
import com.caseeye.base._
import com.caseeye.util.{ ClassUtils => ccu, RichDomainObjectFactory }
import com.novus.casbah.mongodb.Imports._
import java.lang.reflect.{ Field, Type }
import java.sql.Timestamp
import java.util.Date
import org.springframework.util.{ ReflectionUtils }
import org.springframework.util.StringUtils._

object Imports extends Logging {

    val MongoIdField = "_id"

    case class FieldWrapper(field : String, fieldType : Class[_], isIdField : Boolean, value : AnyRef) { override def toString = "field=%s,fieldType=%s,isIdField=%s,value=%s".format(field, fieldType, isIdField, value) }
    case class FieldWrapperWithGeneric(field : Field, fieldType : Class[_], genericType : Type, isIdField : Boolean, isDate : Boolean, value : AnyRef) { override def toString = "field=%s,fieldType=%s,genericType=%s,isIdField=%s,value=%s".format(field, fieldType, genericType, isIdField, value) }

    def dbObj2Entity[T](dbObj : MongoDBObject, entityClass : Class[T]) : T = {
        val entity = entityClass.newInstance
        ccu.getFields(entityClass)
            //filtering the non-persistable fields
            .filter(field => ccu.isPersistableType(field.getType))
            // map the field to case class FieldWrapperWithGeneric
            .map(field => {
                ReflectionUtils.makeAccessible(field)
                val isIdField = ccu.isIdField(field)
                val value =
                    if (isIdField) dbObj.getOrElse(MongoIdField, null)
                    else dbObj.getOrElse(field.getName, null)
                new FieldWrapperWithGeneric(field, field.getType, field.getGenericType, isIdField, classOf[Date].isAssignableFrom(field.getType), value)
            })
            // map the FieldWrapperWithGeneric to (SetMethod->AnyRef)
            .map(fieldWrapper => {
                fieldWrapper match {
                    case FieldWrapperWithGeneric(field, _, _, true, false, idValue : ObjectId) => field -> idValue.toString
                    case FieldWrapperWithGeneric(field, fieldType, _, false, false, null) => field -> null
                    case FieldWrapperWithGeneric(field, fieldType : Class[List[Entity]], genericType, false, false, value : BasicDBList) =>
                        field -> (for (subObj <- value.values) yield {
                            dbObj2Entity(subObj.asInstanceOf[DBObject], ccu.getParameterizedType(genericType).asInstanceOf[Class[Entity]])
                        })
                    case FieldWrapperWithGeneric(field, fieldType : Class[Entity], _, false, false, value : DBObject) =>
                        field -> dbObj2Entity(value, fieldType)
                    case FieldWrapperWithGeneric(field, fieldType : Class[Date], _, false, true, value : Number) =>
                        field -> new Timestamp(value.longValue)
                    case FieldWrapperWithGeneric(field, _, _, false, false, value) =>
                        field -> value
                    case x => log.error("Wrong type of fieldWrapperWithGeneric %s".format(x)); throw new RuntimeException("Wrong type of fieldWrapperWithGeneric")
                }
            })
            //invoke the setMethod with value
            .foreach(mapped => {
                val field = mapped._1
                val value = mapped._2
                if (value != null) { ReflectionUtils.setField(field, entity, value) }
            })

        RichDomainObjectFactory.autowireFactory.autowire(entity)(entity => entity.asInstanceOf[Entity])
    }

    implicit def entity2DBObject(entity : Entity) : DBObject = {
        val mdbo = new BasicDBObject()
        if (entity != null) {
            val entityClass = entity.getClass
            ccu.getFields(entityClass)
                //filterring the non-persistable fiels and fields have no getter method
                .filter(field => ccu.isPersistableType(field.getType))
                // map the field to case class FieldWrapper
                .map(field => {
                    ReflectionUtils.makeAccessible(field)

                    val fieldName = field.getName
                    val fieldType = field.getType
                    val value = ReflectionUtils.getField(field, entity)
                    new FieldWrapper(fieldName, fieldType, ccu.isIdField(field), value)
                })
                // map the FieldWrapper to (String->AnyRef)           
                .map(fieldWrapper => fieldWrapper match {
                    case FieldWrapper(fieldName, _, _, null) =>
                        fieldName -> null
                    case FieldWrapper(fieldName, _, true, value : String) =>
                        MongoIdField -> getObjectId(value)
                    case FieldWrapper(fieldName, fieldType : Class[Entity], false, value : Entity) =>
                        fieldName -> entity2DBObject(value)
                    case FieldWrapper(fieldName, fieldType : Class[List[Entity]], false, value : List[Entity]) =>
                        fieldName -> (for (subEntity <- value) yield entity2DBObject(subEntity))
                    case FieldWrapper(fieldName, fieldType : Class[Date], false, value : Date) =>
                        fieldName -> value.getTime.asInstanceOf[AnyRef]
                    case FieldWrapper(fieldName, _, false, value) =>
                        fieldName -> value
                    case x => log.error("Wrong type of fieldWrapper %s".format(x)); throw new RuntimeException("Wrong type of fieldWrapper")
                })
                // add the mapped result to MongoDBObject
                .foreach(mdbo += _)
        }
        mdbo
    }

    def getObjectId(value : String) = if (value != null && hasText(value.toString)) { val objId = new ObjectId(value.toString); objId.notNew(); objId } else { /*new ObjectId(new Date()) */ null }
    def getFieldName(fieldName : String, superName : String) : String = if (superName == null || superName.length == 0) fieldName else superName+"."+fieldName

    def entity2QueryObj(entity : AnyRef, superName : String = "") : MongoDBObject = {
        val mdbo = MongoDBObject()
        val entityClass = entity.getClass
        ccu.getFields(entityClass)
            .filter(field => ccu.isPersistableType(field.getType))
            .map(field => {
                ReflectionUtils.makeAccessible(field)
                val fieldName = getFieldName(field.getName, superName)
                val fieldType = field.getType
                val value = ReflectionUtils.getField(field, entity)
                new FieldWrapper(fieldName, fieldType, ccu.isIdField(field), value)
            })
            // foreach, append to result           
            .foreach(fieldWrapper =>
                fieldWrapper match {
                    case FieldWrapper(_, _, _, null) => // ignore value == null 
                    case FieldWrapper(_, _, true, value : String) =>
                        mdbo += MongoIdField -> getObjectId(value)
                    case FieldWrapper(fieldName, fieldType : Class[Entity], false, value : Entity) =>
                        mdbo ++= entity2QueryObj(value, fieldName)
                    case FieldWrapper(fieldName, fieldType : Class[List[Entity]], false, value : List[Entity]) =>
                        (for (subEntity <- value) yield entity2QueryObj(subEntity, fieldName)).foreach(mdbo ++= _)
                    case FieldWrapper(fieldName, fieldType : Class[Date], false, value : Date) =>
                        mdbo += fieldName -> value.getTime.asInstanceOf[AnyRef]
                    case FieldWrapper(fieldName, _, false, value) =>
                        mdbo += fieldName -> value
                    case x => log.error("Wrong type of fieldWrapper %s".format(x)); throw new RuntimeException("Wrong type of fieldWrapper")
                })

        mdbo
    }

}